Infinite Web Game Engine Architecture
A comprehensive guide to building a modular, data-driven 2D game engine designed for infinite, chunk-based worlds using HTML5, JavaScript, React, and PixiJS.
1. Overview & Separation of Concerns
To keep the engine scalable and highly modular, the architecture relies on strict separation between three core pillars:
- The Core Engine (PixiJS): The bedrock. Handles rendering, math, and the Entity-Component-System (ECS). It is entirely agnostic to game logic and only knows about raw data.
- The Editor (React): A specialized UI layer that sits on top of the engine. Its primary job is Serialization—allowing visual arrangement of resources and saving them as JSON data.
- The Runtime (Game): The lightweight entry point. It boots the Engine, parses the JSON data exported by the Editor, and executes the game loop.
2. Tech Stack
The chosen technologies leverage the speed of WebGL and the dynamic nature of JavaScript:
- Rendering & Viewport:
PixiJS(Handles the core canvas game loop and batching) - Editor UI:
React(Manages the complex state of the editor interface, scene graphs, and asset browsers) - Modularity: Native ES Modules (
import()) - Data & Serialization: standard JSON
3. Entity-Component-System (ECS) & Memory
To avoid JavaScript Garbage Collection (GC) stutters, the engine uses a Data-Oriented Design.
- Entities: Plain integer IDs (e.g.,
Entity 45). - Components: Stored in flat TypedArrays (like
Float32Array) rather than standard JS objects. This keeps memory contiguous and blazing fast for the CPU. - Systems: Functions that iterate over these flat arrays to update game state.
4. Infinite World & Chunk Management
The world is based on a 32x32 infinite grid. Because you cannot hold an infinite world in memory, data is dynamically loaded and unloaded.
Chunk Storage Map
World data is stored in a JavaScript Map using a string coordinate hash as the key. This allows the world to expand infinitely in any direction, including negative coordinates.
// Example hash key format: "chunkX,chunkY" -> "-1,4"
const chunkMap = new Map();
function getChunkKey(worldX, worldY) {
const chunkX = Math.floor(worldX / (32 * 32));
const chunkY = Math.floor(worldY / (32 * 32));
return `${chunkX},${chunkY}`;
}
Rendering Object Pool
To maintain performance, PixiJS objects are never created or destroyed on the fly. The engine uses an Object Pool.
- Pre-allocate enough Pixi
Containerobjects to cover the screen plus a buffer. - When a chunk leaves the screen, clear its children, return the container to the pool, and save its raw tile data to the
chunkMap. - When a new chunk enters, grab an idle container from the pool and populate it with sprites.
5. The "Pop-In" Plugin System
Libraries (like procedural generation or specialized AI) can simply be dropped into a folder and instantly recognized.
This is achieved using native ES Dynamic Imports. Plugins hook into the engine's Event Bus and chunk lifecycles.
Example: Procedural Generation Hook
// plugins/BiomeGenerator.js
export const plugin = {
id: "procedural-biomes",
init(engine) {
// Hook into the core engine's chunk creation event
engine.chunks.on('chunk_created', (chunk) => {
this.generateTerrain(chunk);
});
},
generateTerrain(chunk) {
// Populate the 32x32 grid with noise data
}
};
6. React & PixiJS Communication Bridge
React (State) and PixiJS (Render Loop) must remain strictly decoupled to protect performance.
They communicate entirely via a lightweight Event Bus or Command Pattern:
- React to PixiJS: React emits events based on user input. For example, selecting a tile in the editor emits
EDITOR_BRUSH_CHANGED. The Pixi loop listens and updates its internal placement state. - PixiJS to React: Pixi emits throttled state events. For example, as the camera pans, it emits
CAMERA_MOVED. React listens to this and updates the coordinate UI in the sidebar without re-rendering the game canvas.